'''自制数据库,用json文件记录数据并提供检索等功能,文件编码是utf-8(无bom).

数据库json格式为{分类(str):[数据记录(dict)]}.
数据记录格式为:{
  'name':名称(str), 
  'tags':[标签(str)], 
  'time':time.time()表示的时间改为整数(int),
  'content':记录主要内容(类型可变), 
  'comment':备注,可以为空(str)
}
这个数据库的设计思想是通过顶层分类对数据进行分类,每一条数据的分类是唯一的,
每条数据可以有多个标签便于检索,并记录数据的时间,增加排序分类的依据.
数据的主要内容可变,这样就能适应更多情况了.
---
外部使用的类:MyDatabase.
该类代表一个数据库,进行数据的写入检索等操作.

作者: setycyas
最后修改: 2020-02-13
'''

import os
import sys
import json

class MyDatabase:
  '''自制数据库类,设计思想与json格式规范见文档顶层注释
  
  ---
  [公有方法]
  构造方法(self, jsonPath = sys.path[0]+r'\database.json'),
    指定读写的数据库文件. 
  add(self, data, _type = 'default'),增加一条数据记录,指定分类.     
  save(self),保存数据库更改到文件.  
  getTags(self),获取标签表与对应记录数量. 
  search(self, _type = None, tags = None, startTime = 0, endTime = -1),
    按条件检索.
  '''
  
  def __init__(self, jsonPath = sys.path[0]+r'\database.json'):
    '''构造方法,读入数据库文件.不存在则新建一个只有default分类的数据库.
    
    @jsonPath: 数据库文件路径.
    '''
    ## 参数初始化记录
    self._path = jsonPath # 保存文件路径
    self._json = {'default':[]} # 读取的数据库,这是默认值
    ## 尝试读取json文件,失败则以默认值保存json文件.
    try:
      with open(self._path, 'rb') as f:
        jsonString = f.read().decode('utf-8')
      tempJson = json.loads(jsonString)
      self._json = tempJson
    except:
      self.save()
    return
    
  ### 公有方法 ###
  
  def save(self):
    '''保存修改到文件
    
    Returns: 成功返回True,失败返回False
    '''
    try:
      print(self._json)
      jsonString = json.dumps(self._json, ensure_ascii = False)
      with open(self._path, 'wb') as f:
        f.write(jsonString.encode('utf-8'))
        print('save成功')
    except Exception as e:
      print('save中出错', e)
      return False
    return True
    
  def add(self, data, _type = 'default'):
    '''新增一条记录并指定分类,注意之后必须用save才能保存到文件
    
    @data: 新增的记录
    @_type: 指定分类
    '''
    if not (_type in self._json):
      self._json[_type] = []
    self._json[_type].append(data)
    return
    
  def getTags(self):
    '''获取所有标签与对应的图片数量
    
    Returns: {标签:图片数量}
    '''
    result = {}
    for _type in self._json:
      for data in self._json[_type]:
        for tag in data['tags']:
          if tag in result:
            result[tag] += 1
          else:
            result[tag] = 1
    return result
  
  ## 检索
  def search(self, _type = None, tags = None, startTime = 0, endTime = -1):
    '''按条件检索记录,返回记录列表.
    
    @_type: 分类(str), None表示全部分类
    @tags: 标签(str)或标签组([str]), None表示全部标签
    @startTime: 时间晚于这个数值才会被检索到
    @endTime: 时间早于这个数值才会被检索到,-1表示不考虑这个项目
    
    Returns: 记录列表[data]
    '''
    result = []
    if (str(type(tags)) == "<class 'str'>"):
      tags = [tags]
    if _type:
      if not _type in self._json:
        return result
      for data in self._json[_type]:
        if self._judge(data, tags, startTime, endTime):
          result.append(data)
    else:
      for __type in self._json:
        for data in self._json[__type]:
          if self._judge(data, tags, startTime, endTime):
            result.append(data)
    return result
  
  ### 私有方法
  
  def _judge(self, data, tags = None, startTime = 0, endTime = -1):
    '''判断一条记录是否符合条件(不含分类).
    
    @data: 要判断的记录
    @tags: 标签条件
    @startTime: 开始时间
    @endTime: 结束时间,-1表示永久
    
    Returns: 符合返回True,否则返回False
    '''
    ## tags判断
    if tags:
      for tag in tags:
        if not (tag in data['tags']):
          return False
    ## 开始时间判断
    if (data['time'] < startTime):
      return False
    ## 结束时间判断
    if ((endTime > 0) and (data['time'] > endTime)):
      return False
    ## 判断结束,未返回则表示符合
    return True
     
  ### 测试用方法
  
  ## 测试添加记录1
  def _addTest1(self):
    for i in range(33, 35):
      _type = 'testType'+str(i%3)
      data = {'name': '测试记录'+str(i), 
        'tags': ['testTag', 'testTag'+str(i%2)],
        'time': int(time.time())+10000*i,
        'content': '测试内容'+str(i),
        'comment': '测试备至'+str(i)
      }
      self.add(data, _type)
    self.save()
    return
    
  ## 检索测试1
  def _searchTest1(self):
    print(self.search(_type = None, tags = 'testTag0', startTime = 0, endTime = -1))
    return
   

if (__name__ == '__main__'):

  import time

  mdb = MyDatabase()
  #mdb._addTest1()
  mdb._searchTest1()